﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using System.Threading;
using Common.Utilities.DLR;

namespace Workflows.Components.Rules
{
    /// <summary>
    /// 
    /// </summary>
    public class CodeRuleWorker:IWorker
    {
        #region 
        private Thread _WorkerThread;
        private CodeRuleInstruction _Instruction;
        private CodeRuleOutput _Output;
        private RunContext _Context;
        private Guid _JobTicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        private static Dictionary<Guid,object> CodeRuleEvaluatorInstances=new Dictionary<Guid, object>();
        #endregion

        #region IWorker Members

        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        public ResultBase Output
        {
            get { return _Output; }
        }

        public Type ComponentType
        {
            get { return typeof(CodeRuleComponent); }
        }

        public RunContext Context
        {
            get
            {
                return _Context;
            }
            set
            {
                _Context = value;
            }
        }

        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (CodeRuleInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._Context = context;
            this._JobTicketID = ticketGuid;
            this._Instruction = (CodeRuleInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }

            this._WorkerThread = Thread.CurrentThread;
            this.ExecuteCodeRule();
        }

        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }

            this._WorkerThread = new Thread(new ThreadStart(this.ExecuteCodeRule));
            this._WorkerThread.Start();
        }

        public void CancelWork()
        {
            if (this._WorkerThread != null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._JobTicketID, this.GetType(), this._Instruction);
                }
            }
        }

        public event WorkerStarted WorkerStartedEvent;

        public event WorkerFinished WorkerFinishedEvent;

        public event WorkerHasError WorkerErredEvent;

        public event WorkerCancelled WorkerCancelledEvent;

        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #endregion

        #region work
        private void ExecuteCodeRule()
        {
            try
            {
                string className = string.Format("CodeRule_{0}", this._Instruction.OwnerComponentID.ToString());
                className = className.Replace("-", "");
                List<string> externalRefs = new List<string>();
                if (!string.IsNullOrEmpty(this._Instruction.InputAssemblyName))
                {
                    string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                    string assemblyFilePath = Path.Combine(binFolderPath, this._Instruction.InputAssemblyName);
                    externalRefs.Add(assemblyFilePath);
                }
                Dictionary<string, object> inputParameters = new Dictionary<string, object>();
                object inputObject = null;
                string serializedObj = this._Instruction.SerializedInputObject;
                if (!string.IsNullOrEmpty(serializedObj))
                {
                    inputObject = this.InstantiateObject(serializedObj);
                    if (inputObject.GetType().FullName != this._Instruction.InputTypeFullName)
                    {
                        throw new Exception("Unable to instantiate input object");
                    }
                }
                inputParameters.Add(this._Instruction.InputTypeFullName, inputObject);
                object codeRuleEvaluatorInstance = null;
                string classCode =
                    CodeRuleWorker.GenerateHeaderCode(this._Instruction) + "\n" +
                    this._Instruction.Code + "\n" +
                    CodeRuleWorker.GenerateFooterCode();

                if (CodeRuleEvaluatorInstances.ContainsKey(this._Instruction.OwnerComponentID))
                {
                    codeRuleEvaluatorInstance = CodeRuleEvaluatorInstances[this._Instruction.OwnerComponentID];
                    if (codeRuleEvaluatorInstance == null)
                    {
                        codeRuleEvaluatorInstance =
                            CodeEvaluation.CreateClassInMemory(
                                className, externalRefs.ToArray(), inputParameters, classCode);
                        CodeRuleEvaluatorInstances[this._Instruction.OwnerComponentID] = codeRuleEvaluatorInstance;
                    }
                }
                else
                {
                    codeRuleEvaluatorInstance =
                        CodeEvaluation.CreateClassInMemory(
                            className, externalRefs.ToArray(), inputParameters, classCode);
                    CodeRuleEvaluatorInstances.Add(this._Instruction.OwnerComponentID, codeRuleEvaluatorInstance);
                }
                bool evaluateResult = CodeEvaluation.ExecuteCodeBlock(ref inputParameters, codeRuleEvaluatorInstance);
                inputObject = inputParameters[this._Instruction.InputTypeFullName];
                serializedObj = this.SerializeObject(inputObject);

                this._Output = new CodeRuleOutput(this._Instruction.OwnerComponentID, this._JobTicketID,
                                                  this._Instruction.InputAssemblyName,
                                                  this._Instruction.InputTypeFullName, serializedObj,
                                                  evaluateResult);

                if (this.WorkerFinishedEvent != null)
                {
                    this.WorkerFinishedEvent(this._JobTicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                {
                    this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction, ex.ToString(),true);
                }
            }

        }

        public static string GenerateHeaderCode(CodeRuleComponent codeRuleComponent)
        {
            string className = string.Format("Class_" + codeRuleComponent.ComponentID.ToString());
            className = className.Replace(".", "");

            StringBuilder block = new StringBuilder();
            block.Append("using System;\n");
            block.Append("using System.Collections.Generic;\n");
            block.Append("using System.ComponentModel;\n");
            block.Append("using System.IO;\n");
            block.Append("using System.Text;\n");
            block.Append("using System.Data;\n");
            block.Append("using System.Xml;\n");
            block.Append("using System.Text.RegularExpressions;\n");
            if (!string.IsNullOrEmpty(codeRuleComponent.InputAssemblyName))
            {
                string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                string assemblyFilePath = Path.Combine(binFolderPath, codeRuleComponent.InputAssemblyName);
                Assembly assembly = Assembly.LoadFrom(assemblyFilePath);
                Type inputType = assembly.GetType(codeRuleComponent.InputTypeFullName);
                string ns = inputType.Namespace;
                block.AppendLine(string.Format("using {0};\n", ns));
            }

            block.AppendLine("namespace MyNamespace {\n");
            block.AppendLine("\tpublic class " + className + " {\n");

            // field declaration 
            block.Append("\t\tprivate " + codeRuleComponent.InputTypeFullName + " _Input;\n");
            // get method
            block.Append("\t\tpublic " + codeRuleComponent.InputTypeFullName + " Get_Input(){\n");
            block.Append("\t\t\treturn _Input;\n");
            block.Append("\t\t}\n");
            // set method
            block.Append("\t\tpublic void Set_Input(" + codeRuleComponent.InputTypeFullName + " value){\n");
            block.Append("\t\t\tthis._Input=value;\n");
            block.Append("\t\t}\n");

            return block.ToString();
        }

        public static string GenerateHeaderCode(CodeRuleInstruction instruction)
        {
            string className = string.Format("Class_" + instruction.OwnerComponentID.ToString());
            className = className.Replace(".", "");

            StringBuilder block = new StringBuilder();
            block.Append("using System;\n");
            block.Append("using System.Collections.Generic;\n");
            block.Append("using System.ComponentModel;\n");
            block.Append("using System.IO;\n");
            block.Append("using System.Text;\n");
            block.Append("using System.Data;\n");
            block.Append("using System.Xml;\n");
            block.Append("using System.Text.RegularExpressions;\n");
            if (!string.IsNullOrEmpty(instruction.InputAssemblyName))
            {
                string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                string assemblyFilePath = Path.Combine(binFolderPath, instruction.InputAssemblyName);
                Assembly assembly = Assembly.LoadFrom(assemblyFilePath);
                Type inputType = assembly.GetType(instruction.InputTypeFullName);
                string ns = inputType.Namespace;
                block.AppendLine(string.Format("using {0};\n", ns));
            }

            block.AppendLine("namespace MyNamespace {\n");
            block.AppendLine("\tpublic class " + className + " {\n");

            // field declaration 
            block.Append("\t\tprivate " + instruction.InputTypeFullName + " _Input;\n");
            // get method
            block.Append("\t\tpublic " + instruction.InputTypeFullName + " Get_Input(){\n");
            block.Append("\t\t\treturn _Input;\n");
            block.Append("\t\t}\n");
            // set method
            block.Append("\t\tpublic void Set_Input(" + instruction.InputTypeFullName + " value){\n");
            block.Append("\t\t\tthis._Input=value;\n");
            block.Append("\t\t}\n");

            return block.ToString();
        }


        public static string GenerateFooterCode()
        {
            return "\n\t}\n}\n";
        }
        #endregion

        #region binary serialization 
        private object InstantiateObject(string serializedObj)
        {
            BinaryFormatter bSerializer2 = new BinaryFormatter();
            string tmpFilePath = FileUtil.GetWindowsAppTempFilePath("CodeRule", "obj");
            FileStream fs2 = File.OpenWrite(tmpFilePath);
            byte[] contents2 = Base64Util.DecodeAsBytes(serializedObj);
            fs2.Write(contents2, 0, contents2.Length);
            fs2.Flush();
            fs2.Close();
            fs2 = File.OpenRead(tmpFilePath);
            object instantiatedObj = bSerializer2.Deserialize(fs2);
            fs2.Close();
            File.Delete(tmpFilePath);

            return instantiatedObj;
        }

        private string SerializeObject(object obj)
        {
            BinaryFormatter bSerializer = new BinaryFormatter();
            string tmpFilePath = FileUtil.GetWindowsAppTempFilePath("CodeRule", "obj");
            FileStream fs = File.OpenWrite(tmpFilePath);
            bSerializer.Serialize(fs, obj);
            fs.Close();

            fs = File.OpenRead(tmpFilePath);
            byte[] contents = new byte[fs.Length];
            fs.Read(contents, 0, contents.Length);
            fs.Close();
            string serializedObj = Base64Util.EncodeAsBase64(contents);
            File.Delete(tmpFilePath);
            return serializedObj;
        }
        #endregion
    }
}
